iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 7
0
自我挑戰組

WebAssembly + Rust 的前端應用系列 第 7

[Day 7] Rust Variables and Mutability 變數和可變性

  • 分享至 

  • xImage
  •  

大家好,上一篇有稍微提到 Rust 的變數預設都是 immutable 的而 rust 的變數類型其實非常的多跟複雜我們今天就用例子來帶各位一步一步了解 rust 的變數有哪些類型吧!

immutable

Rust 的變數預設都是 immutable 也就是不能重新賦值的,例如像是下面這樣的程式碼在編譯時就會報錯。

// In Rust, variables are immutable by default.
fn immutable() {
  let x = 5;
  println!("The value of x is: {}", x);
  x = 6;
  ^^^^^ cannot assign twice to immutable variable
}

mutable

反之那麼當然也有 mutable 即可以修改的變數。

fn mutable() {
  let mut x = 5;
  println!("The value of x is: {}", x);
  // The value of x is: 5
  x = 6;
  println!("The value of x is: {}", x);
  // The value of x is: 6
}

這邊我就開始好奇一件事情那麼為什麼 rust 的變數預設要是 immutable 的呢?因為這跟我寫 JS 的時候差異非常的大所以我就去找了一些資料。

首先為什麼要 immutable 這點我相信如果熟悉 javascript 或是其他語言的人都不可否認的一個問題就是 mutable 更容易造成不可預期的錯誤而這種錯誤是當你有越多 mutable 變數時他 debug 的難度會是指數成長,接著是可讀性當你知道這個變數是 immutable 的時候你就不用再多找好幾層或是去找他產生的邏輯,最後就是符合 rust 的設計目標在多線程的時候可以避免錯誤,例如當多線程的時候用 mutable 的變數就有可能造成 data race

但是有個重點就是 immutable 的效能比較差當然如果你的資料結構簡單那麼不會有太大的影響但是當你的資料結構比較複雜的時候 mutable 的效能就會比較好,所以在設計軟體的時候該怎麼權衡利弊得失就看各位的智慧了。

variables vs constants

在 Rust 裡面變數也可以是 const(constants) 我們可能已經在其他語言裡面看過(constants) 這個概念像是 javascript es6 也有 const 的變數類型。那麼先讓我們看一段官方的說明,

... constants. Like immutable variables, constants are values that are bound to a name and are not allowed to change, but there are a few differences between constants and variables.

First, you aren’t allowed to use mut with constants. Constants aren’t just immutable by default—they’re always immutable.

接著讓我們用程式碼來示例會更清楚。

const GLOBAL_CONSTANTS: &str = "Hello, world!";

fn main() {
  println!("The value of GLOBAL_CONSTANTS is: {}", GLOBAL_CONSTANTS);
}

上面這段程式碼有兩個重點

  • const 的變數必須宣告 type
  • const 可以在任何 scope 宣告
const GLOBAL_CONSTANTS: &str = "Hello, world!";
let test = 123;
^^^ expected item

fn main() {
  ...
}

Note: 上面這段程式碼會報錯,因為一般的變數不能宣告在全域。

然後由於 const 本身設計就是直接寫在 memory 的特定位置因此不能在 runtime (執行時間) 時才去分配他的值,而這個概念其實也就是靜態變數(static)或是我們稱做全域變數,在大多數的用例中 const 可以處理幾乎所有情況但是 Rust 本身也有提供 static 的宣告方式。為了避免混淆這個又是怎麼回事呢?我們看下面的範例

static mut GLOBAL_STATIC: &str = "Hello, world!";
fn main() {
  unsafe {
    GLOBAL_STATIC = "Changed !";
    println!("The value of GLOBAL_STATIC is: {}", GLOBAL_STATIC);
  }
}

原來最大的差異就是上面有提到過的 const 一定是 immutable 的而 static 可以是 mutable 的但是必須搭配 unsafe 的 block 才可以使用。

Note: 在 runtime 給全域變數賦值是不安全的做法我們應該要盡量避免。

Shadowing

一般的語言我們不能重複宣告變數但是在 Rust 可以而其稱作 shadowing 我們先來看一段程式碼。

fn variables_shadowing() {
  let x = 5;

  let x = x + 1;

  let x = x * 2;

  println!("The value of x is: {}", x);
  // The value of x is: 12
}

相信第一次看到這種寫法的同學都會黑人問號吧?我是非常好奇既然 rust 預設的變數是 immutable 的但是為什麼又提供 shadowing 這種方式可以給變數重新賦值呢?

假設沒有 shadowing 根據上面的例子我們該怎麼寫這支程式?

fn variables_shadowing() {
  let x = 5;

  let y = x + 1;

  let z = y * 2;

  println!("The value of z is: {}", z);
  // The value of z is: 12
}

上面可以看到我們必須多宣告 2 個變數才能處理這樣的情況而且假如 x 跟 y 用完之後就不需要了我們還是必須多佔用這兩個變數的空間。

因此 rust 提供的 shadowing 的方式就是在編譯時它自動地幫你做好上面的流程並且不會多佔用那兩個變數的空間。

shadowing vs mutable

那麼既然都是可以重新賦值 shadowing 和 mutable 的差異又在哪邊呢?

  • shadowing 的 datatype 可以改變但是 mutable 則不行
  • shadowing 重新賦值之後還是維持 immutable

但是我猜其實在編譯成機械碼之後是一樣的(組合語言我不熟請高手指教)但是在編譯期間如果語法錯的話編譯就過不了可以大幅降低錯誤發生的機率。

總結

從學習編譯式語言開始記憶體位置的理解就變得重要許多,我們要開始慢慢的有這個概念未來在理解 Rust 的設計概念時會有幫助。Rust 在處理變數上下了不少功夫但是確實也比較複雜,甚至是後面再把 reference 加近來討論的話又會更加複雜建議對變數還不熟的同學要多看幾遍。

以上今天的文章就到此為止,那麼我們明天見。

最後一樣有問題歡迎發問

/images/emoticon/emoticon07.gif

參考連結

variables-and-mutability
differences-between-variables-and-constants
rust-review-immutable-by-default
wiki static variable
what-is-the-difference-between-a-const-variable-and-a-static-variable


上一篇
[Day 6] Rust Programming a Guessing Game 終極密碼
下一篇
[Day 8] Rust Data Types 資料型態 (1)
系列文
WebAssembly + Rust 的前端應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
DanSnow
iT邦好手 1 級 ‧ 2021-04-06 11:53:10

總算把整個系列都看完了,到最後還是沒有 wasm 有點可惜,這邊覺得有個地方有問題想要討論下, shadowing 並沒有不允許改變 mutability ,實際上在程式語言中變數的名字最主要的用途是讓程式設計師使用的,在產生出來的機器碼中根本就不會有這些資訊,所以 Rust 這邊它就直接允許你把同一個名字重新指向另一個變數,這就是 shadowing ,但這在 Rust 的角度而言,跟一般的變數宣告其實沒有兩樣,只是原本同名的變數的名字已經被拿去重新使用,且沒有新的名字指向它而變的無法使用而已 playground link ,而且不只是 mutability ,也可以試試這樣的程式碼

let a = 1;
let a = &a;

我要留言

立即登入留言